package com.demo.encryptionDecryption.rsa;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * @author qingqing.liang
 * @create 2017-12-18
 **/
public class RsaUtil {
    private static final String KEY_ALGORITHM = "RSA";
    private static final String SIGNATURE_ALGORITHM = "MD5withRSA";
    private static final String DEFAULT_ENCODING = "UTF-8";


    private static byte[] decryptBASE64(String key) {
        return Base64.decodeBase64(key);
    }

    private static String encryptBASE64(byte[] bytes) {
        return Base64.encodeBase64String(bytes);
    }
    /**
     * 用私钥对信息生成数字签名
     *
     * @param data       加密数据
     * @param privateKey 私钥
     * @return
     * @throws Exception
     */
    public static String sign(String data, String privateKey) throws Exception {


        // 解密由base64编码的私钥
        byte[] keyBytes = decryptBASE64(privateKey);
        // 构造PKCS8EncodedKeySpec对象
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        // KEY_ALGORITHM 指定的加密算法
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        // 取私钥匙对象
        PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
        // 用私钥对信息生成数字签名
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(priKey);
        signature.update(data.getBytes());
        return encryptBASE64(signature.sign());
    }

    /**
     * 校验数字签名
     *
     * @param data      加密数据
     * @param publicKey 公钥
     * @param sign      数字签名
     * @return 校验成功返回true 失败返回false
     * @throws Exception
     */
    public static boolean verify(String data, String publicKey, String sign)
            throws Exception {
        // 解密由base64编码的公钥
        byte[] keyBytes = decryptBASE64(publicKey);
        // 构造X509EncodedKeySpec对象
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        // KEY_ALGORITHM 指定的加密算法
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        // 取公钥匙对象
        PublicKey pubKey = keyFactory.generatePublic(keySpec);
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initVerify(pubKey);
        signature.update(data.getBytes());
        // 验证签名是否正常
        return signature.verify(decryptBASE64(sign));
    }


    public static String encryptByPublicKey(String data, String publicKey) {
        try {
            byte[] dataByte = data.getBytes(DEFAULT_ENCODING);
            byte[] keyBytes = Base64.decodeBase64(publicKey);
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            PublicKey publicK = keyFactory.generatePublic(x509KeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(1, publicK);
            int inputLen = dataByte.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;

            for(int i = 0; inputLen - offSet > 0; offSet = i * 117) {
                byte[] cache;
                if(inputLen - offSet > 117) {
                    cache = cipher.doFinal(dataByte, offSet, 117);
                } else {
                    cache = cipher.doFinal(dataByte, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                ++i;
            }

            byte[] encryptedData = out.toByteArray();
            out.close();
            return Base64.encodeBase64String(encryptedData);
        } catch (Exception e) {
           e.printStackTrace();
           return null;
        }
    }


    public static String decryptByPublicKey(String encryptedData, String publicKey) {
        try {
            byte[] encryptedDateByte = Base64.decodeBase64(encryptedData);
            byte[] keyBytes = Base64.decodeBase64(publicKey);

            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            PublicKey publicK = keyFactory.generatePublic(x509KeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(2, publicK);
            int inputLen = encryptedDateByte.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;

            for(int i = 0; inputLen - offSet > 0; offSet = i * 128) {
                byte[] cache;
                if(inputLen - offSet > 128) {
                    cache = cipher.doFinal(encryptedDateByte, offSet, 128);
                } else {
                    cache = cipher.doFinal(encryptedDateByte, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                ++i;
            }
            byte[] decryptedData = out.toByteArray();
            out.close();
            return new String(decryptedData, DEFAULT_ENCODING);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static String decryptByPrivateKey(String encryptText, String privateKey) {
        try {
            byte[] encryptTextByte = Base64.decodeBase64(encryptText);
            byte[] privateKeyBytes = Base64.decodeBase64(privateKey);
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
            byte[] decryptedData = null;
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(2, privateK);
            int inputLen = encryptTextByte.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;

            for(int i = 0; inputLen - offSet > 0; offSet = i * 128) {
                byte[] cache;
                if(inputLen - offSet > 128) {
                    cache = cipher.doFinal(encryptTextByte, offSet, 128);
                } else {
                    cache = cipher.doFinal(encryptTextByte, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                ++i;
            }

            decryptedData = out.toByteArray();
            out.close();
            return new String(decryptedData, DEFAULT_ENCODING);
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }

    public static String encryptByPrivateKey(String plainText, String privateKey) {
        try {
            byte[] plainTextBytes = plainText.getBytes(DEFAULT_ENCODING);
            byte[] privateKeyBytes = Base64.decodeBase64(privateKey);
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(1, privateK);
            int inputLen = plainTextBytes.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;

            for(int i = 0; inputLen - offSet > 0; offSet = i * 117) {
                byte[] cache;
                if(inputLen - offSet > 117) {
                    cache = cipher.doFinal(plainTextBytes, offSet, 117);
                } else {
                    cache = cipher.doFinal(plainTextBytes, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                ++i;
            }

            byte[] encryptedData = out.toByteArray();
            out.close();
            return Base64.encodeBase64String(encryptedData);
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}
